/*
 *  Axolotl - Alternate persistence for Grails
 *  Copyright (C) 2008  Juanjo García Latre
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.googlecode.axolotl.query.groovy;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

import com.googlecode.axolotl.query.And;
import com.googlecode.axolotl.query.Between;
import com.googlecode.axolotl.query.Conjunction;
import com.googlecode.axolotl.query.Criterion;
import com.googlecode.axolotl.query.Equals;
import com.googlecode.axolotl.query.GreaterOrEqual;
import com.googlecode.axolotl.query.In;
import com.googlecode.axolotl.query.LessOrEqual;
import com.googlecode.axolotl.query.Like;
import com.googlecode.axolotl.query.Or;
import com.googlecode.axolotl.query.Order;
import com.googlecode.axolotl.query.PropertyName;
import com.googlecode.axolotl.query.Query;
import com.googlecode.axolotl.query.QueryVisitor;


/**
 * Renders a query in a Groovy DSL syntax compatible with QueryBuilder.
 *
 * @author juanjo
 */
public class QuerySerializer implements QueryVisitor<CharSequence> {
	
	public String visit(PropertyName propertyName) {
		return propertyName.getCanonicalName();
	}

	
	public CharSequence visit(And and) {
		return render(and, "and");
	}

	
	public CharSequence visit(Or or) {
		return render(or, "or");
	}

	
	public CharSequence visit(Equals equ) {
		StringBuilder buf = new StringBuilder();
		buf.append( equ.getProperty().accept(this) ).append("(eq:");
		buf.append( renderValue(equ.getValue()) ).append(')');
		if (equ.isNegated()) {
			buf.insert(0, "not {").append('}');
		}
		return buf;
	}
	

	public CharSequence visit(GreaterOrEqual ge) {
		PropertyName prp = ge.getProperty();
		Object value = ge.getValue();

		StringBuilder buf = new StringBuilder();
		buf.append( prp.accept(this) ).append(ge.isNegated() ? "(lt:" : "(ge:");
		buf.append( renderValue(value) ).append(')');
		return buf;
	}
	

	public CharSequence visit(LessOrEqual le) {
		PropertyName prp = le.getProperty();
		Object value = le.getValue();

		StringBuilder buf = new StringBuilder();
		buf.append( prp.accept(this) ).append(le.isNegated() ? "(gt:" : "(le:");
		buf.append( renderValue(value) ).append(')');
		return buf;
	}

	
	public CharSequence visit(Between btw) {
		PropertyName prp = btw.getProperty();
		Object valueFrom = btw.getFromValue();
		Object valueTo = btw.getToValue();

		StringBuilder buf = new StringBuilder();
		buf.append( String.valueOf(prp.accept(this)) ).append("(between:[");
		buf.append( renderValue(valueFrom) ).append(',');
		buf.append( renderValue(valueTo) ).append("])");

		if (btw.isNegated()) {
			buf.insert(0, "not {").append('}');
		}
		return buf;
	}

	
	public CharSequence visit(Like lk) {
		StringBuilder buf = new StringBuilder();
		buf.append( lk.getProperty().accept(this) ).append("(like:");
		buf.append( renderValue(lk.getPattern()) ).append(')');

		if (lk.isNegated()) {
			buf.insert(0, "not {").append('}');
		}
		return buf;
	}
	

	public CharSequence visit(In in) {
		StringBuilder buf = new StringBuilder();
		buf.append( in.getProperty().accept(this) ).append("(in:[");

		Object value = in.getValue();
		Collection items = value.getClass().isArray() ? 
				Arrays.asList((Object[]) value) : (Collection) value;

		for (Iterator i = items.iterator(); i.hasNext();) {
			buf.append( renderValue(i.next()) );
			if (i.hasNext())
				buf.append(',');
		}

		buf.append("])");

		if (in.isNegated()) {
			buf.insert(0, "not {").append('}');
		}
		return buf;
	}

	
	public CharSequence visit(Order order) {
		StringBuilder buf = new StringBuilder();
		buf.append("orderBy {");

		Order o = order;
		do {
			buf.append( o.getProperty().accept(this) );
			buf.append( o.isAscendant() ? "('asc')" : "('desc')" );
			if (o.getNext() != null)
				buf.append(';');

			o = o.getNext();
		} while (o != null);

		buf.append('}');
		return buf;
	}

	
	public CharSequence visit(Query query) {
		StringBuilder buf = new StringBuilder("query {");
		buf.append( query.getType().getName() ).append(' ');
		
		if (query.getCompiledFilter() != null) {
			buf.append( query.getCompiledFilter().accept(this) ).append(';');
		}
		if (query.getCompiledOrder() != null) {
			buf.append( query.getCompiledOrder().accept(this) );
		}
		buf.append('}');
		return buf;
	}
	
	
	protected CharSequence render(Conjunction conj, String oper) {
		StringBuilder buf = new StringBuilder();

		if (conj.getCriteria().size() > 0) {
			buf.append(oper).append(" {");

			for (Criterion criterion: conj.getCriteria()) {
				buf.append( criterion.accept(this) ).append("; ");
			}

			buf.append('}');

			if (conj.isNegated()) {
				buf.insert(0, "not {").append('}');
			}
		}
		return buf;
	}

	
	protected CharSequence renderValue(Object value) {
		if (value instanceof CharSequence)
			return new StringBuilder("\"").append(value).append("\"");
		else
			return String.valueOf(value);
	}

}
